﻿#include "HexagonMineGame.h"
#include <assert.h>

#include <algorithm>
#include <cstdlib>

namespace hexagon_mine
{
	CHexagonMineGame::CHexagonMineGame()
	{
		Config config;
		config.RandomSize = true;
		config.UseDetector = true;
		config.HexagonShape = false;
		config.AutoLockMine = false;
		config.HasRightSideMine = false;
		config.Width = 7;
		config.Height = 9;
		config.MinePercent = 15;

		bool set = SetConfig(config);
		assert(set);

		m_VectorWidth = 7;
		m_VectorHeight = 9;

		m_GameObserver = 0;
		m_ShowObserver = 0;

		m_IsStarted = false;
		m_HasLaid = false;

		m_HasDetector = false;
	}

	CHexagonMineGame::~CHexagonMineGame()
	{
	}

	CHexagonMineGame::Pos CHexagonMineGame::HolePosToVector(CHexagonMineGame::Pos pos) const
	{
		Pos vec;
		vec.y = - pos.y + m_HoleYOffset;
		int offset = (pos.y + 1 - m_HoleYOffset % 2) >> 1;
		vec.x = pos.x + m_HoleXOffset - offset;
		
		return vec;
	}

	CHexagonMineGame::Pos CHexagonMineGame::VectorPosToHole(CHexagonMineGame::Pos pos) const
	{
		Pos hole;
		hole.y = - pos.y + m_HoleYOffset;
		int offset = (hole.y + 1 - m_HoleYOffset % 2) >> 1;
		hole.x = pos.x - m_HoleXOffset + offset;

		return hole;
	}

	void CHexagonMineGame::VectorPosMove(CHexagonMineGame::Pos& pos, int dir) const
	{
		Pos hole = VectorPosToHole(pos);
		HolePosMove(hole, dir);
		pos = HolePosToVector(hole);
	}

	void CHexagonMineGame::HolePosMove(CHexagonMineGame::Pos& pos, int dir) const
	{
		assert((dir >= 0) && (dir <= 5));

		switch (dir)
		{
		case 0:
			++pos.x;
			break;
		case 1:
			++pos.x;
			++pos.y;
			break;
		case 2:
			++pos.y;
			break;
		case 3:
			--pos.x;
			break;
		case 4:
			--pos.x;
			--pos.y;
			break;
		case 5:
			--pos.y;
			break;
		}
	}

	bool CHexagonMineGame::IsHoleValid(CHexagonMineGame::Pos hole) const
	{
		return IsHoleValidByVector(HolePosToVector(hole));
	}

	CHexagonMineGame::Hole& CHexagonMineGame::GetHole(CHexagonMineGame::Pos hole)
	{
		return GetHoleByVector(HolePosToVector(hole));
	}

	const CHexagonMineGame::Hole& CHexagonMineGame::GetHole(CHexagonMineGame::Pos hole) const
	{
		return GetHoleByVector(HolePosToVector(hole));
	}

	bool CHexagonMineGame::IsHoleValidByVector(CHexagonMineGame::Pos vec) const
	{
		if ((vec.x >= 0) && (vec.x < m_VectorWidth)
			&& (vec.y >= 0) && (vec.y < m_VectorHeight))
		{
			return GetHoleByVector(vec).valid;
		}
		else
		{
			return false;
		}
	}

	CHexagonMineGame::Hole& CHexagonMineGame::GetHoleByVector(CHexagonMineGame::Pos vec)
	{
		return m_Holes[vec.y][vec.x];
	}

	const CHexagonMineGame::Hole& CHexagonMineGame::GetHoleByVector(CHexagonMineGame::Pos vec) const
	{
		return m_Holes[vec.y][vec.x];
	}

	void CHexagonMineGame::CalcAndPrepareHoles()
	{
		if (m_Config.RandomSize)
		{
			m_Config.Width = rand() % (MAX_WIDTH + 1 - 3) + 3;
			m_Config.Height = rand() % (MAX_HEIGHT + 1 - 3) + 3;
		}

		if (m_Config.HexagonShape)
		{
			if (m_Config.Width % 2 == 0)
				--m_Config.Width;
			m_Config.Height = m_Config.Width;
		}

		m_VectorWidth = m_Config.Width;
		if (m_Config.HexagonShape && (m_Config.Height / 2 % 2 == 1))
			++m_VectorWidth;
		m_VectorHeight = m_Config.Height;

		int width = m_VectorWidth;
		int height = m_VectorHeight;

		m_HoleXOffset = m_VectorWidth / 2;
		m_HoleYOffset = m_VectorHeight / 2;

		m_TotalHoles = 0;

		int total = 0;

		if (m_Config.HexagonShape)
		{
			m_Holes.resize(height);
			for (int y = 0; y < height; ++y)
			{
				Hole h = {false, 0, HMHS_COVER, 0};
				m_Holes[y].clear();
				m_Holes[y].resize(width, h);

				int y_diff = abs(y - m_HoleYOffset);
				int hole_width = m_Config.Width - y_diff;
				int x_start = (m_HoleYOffset % 2 + y_diff) / 2;
				assert(hole_width + x_start <= m_VectorWidth);
				for (int x = 0; x < hole_width; ++x)
				{
					m_Holes[y][x + x_start].valid = true;
					++total;
				}
			}
		}
		else
		{
			m_Holes.resize(height);
			for (int y = 0; y < height; ++y)
			{
				Hole h = {true, 0, HMHS_COVER, 0};
				m_Holes[y].clear();
				m_Holes[y].resize(width, h);
				total += width;

				if (!m_Config.HasRightSideMine && (y % 2 == 1))
				{
					m_Holes[y][width - 1].valid = false;
					--total;
				}
			}
		}

		m_TotalHoles = total;
		m_TotalMines = m_TotalHoles * m_Config.MinePercent / 100;
		if (m_TotalMines == 0)
			m_TotalMines = 1;
		else if (m_TotalMines >= m_TotalHoles)
			m_TotalMines = m_TotalHoles - 1;

		m_RemainMines = m_TotalMines;
		m_LockedMines = 0;
		m_OpenHoles = 0;

		m_IsStarted = true;
		m_HasLaid = false;

		m_HasDetector = m_Config.UseDetector;

		NotifyInit();
		NotifyShow();
		NotifyInfo();
	}

	void CHexagonMineGame::Lay()
	{
		Pos vec = {-1, -1};
		Lay(vec);
	}

	void CHexagonMineGame::Lay(Pos vec)
	{
		std::vector<Pos> positions;
		positions.reserve(m_TotalHoles);

		bool vec_match = false;

		for (size_t y = 0; y < m_Holes.size(); ++y)
		{
			std::vector<Hole>& line = m_Holes[y];
			for (size_t x = 0; x < line.size(); ++x)
			{
				Hole& hole = line[x];
				if (hole.valid)
				{
					if ((x == vec.x) && (y == vec.y))
					{
						vec_match = true;
					}
					else
					{
						Pos p = {x, y};
						positions.push_back(p);
					}

					hole.value = 0;
				}
			}
		}

		size_t num_mines = m_TotalMines;
		if (num_mines > positions.size())
			num_mines = positions.size();

		std::random_shuffle(positions.begin(), positions.end());

		for (size_t i = 0; i < num_mines; ++i)
		{
			Pos vec = positions[i];
			m_Holes[vec.y][vec.x].value = -1;
		}

		for (size_t y = 0; y < m_Holes.size(); ++y)
		{
			std::vector<Hole>& line = m_Holes[y];
			for (size_t x = 0; x < line.size(); ++x)
			{
				Hole& hole = line[x];
				if (hole.valid && (hole.value != -1))
				{
					int count = 0;
					int not_mine = 0;

					Pos vec = {x, y};
					for (int d = 0; d < 6; ++d)
					{
						Pos n = vec;
						VectorPosMove(n, d);
						if (IsHoleValidByVector(n))
						{
							const Hole& h = GetHoleByVector(n);
							if (h.value == -1)
								++count;
							else
								++not_mine;
						}
					}

					hole.value = count;
					hole.left_not_mine = not_mine;
				}
			}
		}

		m_HasLaid = true;
	}

	bool CHexagonMineGame::Start()
	{
		m_HasLaid = false;

		CalcAndPrepareHoles();
		//Lay();

		return true;
	}

	bool CHexagonMineGame::Operate(int x, int y, CHexagonMineGame::OPERATE_TYPE op)
	{
		assert(m_IsStarted);
		Pos vec = {x, y};

		if (!IsHoleValidByVector(vec))
			return false;

		switch (op)
		{
		case HMGO_DIG:
			Dig(vec);
			break;
		case HMGO_MARK:
			Mark(vec);
			break;
		case HMGO_QUESTION:
			Question(vec);
			break;
		case HMGO_DETECT:
			Detect(vec);
			break;
		default:
			assert(0 && L"未知操作符");
		}

		if ((m_OpenHoles + m_TotalMines == m_TotalHoles)
			|| (m_LockedMines == m_TotalMines))
		{
			Win();
		}

		NotifyShowChanged();
		NotifyInfo();

		return true;
	}

	int CHexagonMineGame::QueryOperation(int x, int y) const
	{
		if (!m_IsStarted)
			return 0;

		Pos vec = {x, y};
		if (IsHoleValidByVector(vec))
		{
			const Hole& hole = GetHoleByVector(vec);
			switch (hole.status)
			{
			case HMHS_COVER:
				return HMGO_DIG | HMGO_QUESTION | ((m_RemainMines > 0) ? HMGO_MARK : 0);
				break;
			case HMHS_MARK:
				return HMGO_MARK | HMGO_QUESTION;
				break;
			case HMHS_QUESTION:
				return HMGO_QUESTION | ((m_RemainMines > 0) ? HMGO_MARK : 0);
				break;
			default:
				return 0;
			}
		}

		return 0;
	}

	void CHexagonMineGame::Dig(CHexagonMineGame::Pos vec_pos)
	{
		if (!m_HasLaid)
		{
			Lay(vec_pos);
		}

		assert(IsHoleValidByVector(vec_pos));
		Hole& hole = GetHoleByVector(vec_pos);

		if (hole.status == HMHS_COVER)
		{
			if (hole.value == -1)
			{
				Die(vec_pos);
			}
			else if (hole.value == 0)
			{
				DeepDig(vec_pos);
			}
			else
			{
				ChangeState(vec_pos, hole, HMHS_OPEN);
			}
		}
	}

	void CHexagonMineGame::Mark(CHexagonMineGame::Pos vec_pos)
	{
		assert(IsHoleValidByVector(vec_pos));
		Hole& hole = GetHoleByVector(vec_pos);

		switch (hole.status)
		{
		case HMHS_COVER:
		case HMHS_QUESTION:
			ChangeState(vec_pos, hole, HMHS_MARK);
			break;
		case HMHS_MARK:
			ChangeState(vec_pos, hole, HMHS_COVER);
			break;
		default:
			return;
		}
	}

	void CHexagonMineGame::Question(CHexagonMineGame::Pos vec_pos)
	{
		assert(IsHoleValidByVector(vec_pos));
		Hole& hole = GetHoleByVector(vec_pos);

		switch (hole.status)
		{
		case HMHS_MARK:
			ChangeState(vec_pos, hole, HMHS_QUESTION);
			break;
		case HMHS_QUESTION:
			ChangeState(vec_pos, hole, HMHS_COVER);
			break;
		default:
			return;
		}
	}

	void CHexagonMineGame::Detect(CHexagonMineGame::Pos vec_pos)
	{
		if (!m_HasLaid)
		{
			Lay();
		}

		assert(m_HasDetector && L"已经没有探测针！");
		if (!m_HasDetector)
			return;

		assert((m_RemainMines > 0) && L"没有雷标的情况下不能探测！");
		if (m_RemainMines == 0)
			return;

		assert(IsHoleValidByVector(vec_pos));
		Hole& hole = GetHoleByVector(vec_pos);

		switch (hole.status)
		{
		case HMHS_COVER:
		case HMHS_MARK:
		case HMHS_QUESTION:
			break;
		default:
			return;
		}

		if (hole.value == -1)
		{
			ChangeState(vec_pos, hole, HMHS_BOMB);
		}
		else if (hole.value >= 0)
		{
			if (hole.status != HMHS_COVER)
				ChangeState(vec_pos, hole, HMHS_COVER);
			Dig(vec_pos);
		}

		m_HasDetector = false;
	}

	bool CHexagonMineGame::ChangeState(CHexagonMineGame::Pos pos, CHexagonMineGame::Hole& hole, HOLE_STATUS status)
	{
		assert(hole.status != status);
		if (hole.status == status)
			return false;

		if (hole.status == HMHS_MARK)
		{
			++m_RemainMines;
			assert(m_RemainMines <= m_TotalMines);
		}

		hole.status = status;

		if (hole.status == HMHS_OPEN)
		{
			assert(hole.value >= 0);
			++m_OpenHoles;

			if (m_Config.AutoLockMine)
			{
				// 下面为自动锁定雷所进行的操作
				for (int i = 0; i < 6; ++i)
				{
					Pos round = pos;
					VectorPosMove(round, i);
					if (!IsHoleValidByVector(round))
						continue;

					Hole& round_hole = GetHoleByVector(round);
					if (round_hole.value == -1)
						continue;

					--round_hole.left_not_mine;
					assert(round_hole.left_not_mine >= 0);
					if ((round_hole.status == HMHS_OPEN) && (round_hole.left_not_mine == 0))
						MarkAllMines(round);
				}

				if (hole.left_not_mine == 0)
					MarkAllMines(pos);
			}
		}
		else if (hole.status == HMHS_MARK)
		{
			--m_RemainMines;
			// Die 之后，可能 m_RemainMines 会小于 0
			assert(!m_IsStarted || (m_RemainMines >= 0));
		}
		else if (hole.status == HMHS_BOMB)
		{
			--m_RemainMines;
			++m_LockedMines;
			assert(m_LockedMines <= m_TotalMines);
		}

		m_ChangedHoles.push_back(pos);

		return true;
	}

	void CHexagonMineGame::MarkAllMines(CHexagonMineGame::Pos pos)
	{
		assert(IsHoleValidByVector(pos) 
			&& (GetHoleByVector(pos).left_not_mine == 0)
			&& (GetHoleByVector(pos).status == HMHS_OPEN));

		for (int i = 0; i < 6; ++i)
		{
			Pos round = pos;
			VectorPosMove(round, i);
			if (!IsHoleValidByVector(round))
				continue;
			Hole& round_hole = GetHoleByVector(round);
			if (round_hole.value != -1)
				continue;

			if ((round_hole.status != HMHS_BOMB) && (m_RemainMines > 0))
			{
				ChangeState(round, round_hole, HMHS_BOMB);
			}
		}
	}

	void CHexagonMineGame::Win()
	{
		m_IsStarted = false;

		ClearShow();

		NotifyFinish(true);
	}

	void CHexagonMineGame::Die(CHexagonMineGame::Pos vec_pos)
	{
		Hole& hole = GetHoleByVector(vec_pos);
		ChangeState(vec_pos, hole, HMHS_BROKEN);

		m_IsStarted = false;

		ClearShow();

		NotifyFinish(false);
	}

	void CHexagonMineGame::ClearShow()
	{
		for (size_t y = 0; y < m_Holes.size(); ++y)
		{
			for (size_t x = 0; x < m_Holes[y].size(); ++x)
			{
				Pos pos = {x, y};
				if (!IsHoleValidByVector(pos))
					continue;

				Hole& hole = GetHoleByVector(pos);
				if (hole.value == -1)
				{
					if (hole.status == HMHS_BROKEN)
						continue;
					if (hole.status == HMHS_BOMB)
						continue;

					ChangeState(pos, hole, HMHS_BOMB);
				}
			}
		}
	}

	void CHexagonMineGame::DeepDig(CHexagonMineGame::Pos vec_pos)
	{
		std::vector<Pos> zeros;

		if (true)
		{
			Hole& hole = GetHoleByVector(vec_pos);
			assert((hole.value == 0) && (hole.status != HMHS_OPEN));

			ChangeState(vec_pos, hole, HMHS_OPEN);

			zeros.push_back(vec_pos);
		}

		while (!zeros.empty())
		{
			Pos v = zeros.back();
			zeros.pop_back();

			for (int i = 0; i < 6; ++i)
			{
				Pos p = v;
				VectorPosMove(p, i);

				if (IsHoleValidByVector(p))
				{
					Hole& next = GetHoleByVector(p);
					assert(next.value != -1);
					if (next.status == HMHS_OPEN)
						continue;

					ChangeState(p, next, HMHS_OPEN);

					if (next.value == 0)
						zeros.push_back(p);
				}
			}
		}
	}

	void CHexagonMineGame::NotifyInit()
	{
		if (m_ShowObserver)
		{
			bool left_side = true;
			if (m_Config.HexagonShape && (m_Config.Height / 2 % 2 == 1))
				left_side = false;
			bool right_side = m_Config.HasRightSideMine;
			m_ShowObserver->Init(m_VectorWidth, m_VectorHeight, true, left_side, right_side);
		}
	}

	void CHexagonMineGame::NotifyShow()
	{
		m_ChangedHoles.reserve(m_TotalHoles);

		for (size_t y = 0; y < m_Holes.size(); ++y)
		{
			for (size_t x = 0; x < m_Holes[y].size(); ++x)
			{
				Pos pos = {x, y};
				if (!IsHoleValidByVector(pos))
					continue;

				m_ChangedHoles.push_back(pos);
			}
		}

		NotifyShowChanged();
	}

	void CHexagonMineGame::NotifyShowChanged()
	{
		if (m_ShowObserver && !m_ChangedHoles.empty())
		{
			std::vector<Pos> vec_pos;
			vec_pos.swap(m_ChangedHoles);

			std::vector<IGameShowObserver::HoleInfo> v;
			v.reserve(vec_pos.size());

			for (size_t i = 0; i < vec_pos.size(); ++i)
			{
				const Pos& vec = vec_pos[i];
				const Hole& hole = GetHoleByVector(vec);

				IGameShowObserver::HoleInfo info = {vec.x, vec.y, hole.value, hole.status};
				v.push_back(info);
			}

			m_ShowObserver->Update(v);
		}
	}

	void CHexagonMineGame::NotifyInfo()
	{
		GameInfo info;
		info.OpenHoles = m_OpenHoles;
		info.RemainMines = m_RemainMines;
		info.TotalMines = m_TotalMines;
		info.TotalHoles = m_TotalHoles;

		if (m_GameObserver)
		{
			m_GameObserver->Info(info);
		}
	}

	void CHexagonMineGame::NotifyFinish(bool win)
	{
		if (m_GameObserver)
		{
			m_GameObserver->Finish(win);
		}
	}

	bool CHexagonMineGame::SetSize(int width, int height)
	{
		if ((width >= 3) && (width <= MAX_WIDTH)
			&& (height >= 3) && (height <= MAX_HEIGHT))
		{
			m_Config.Width = width;
			m_Config.Height = height;

			return true;
		}
		else
		{
			return false;
		}
	}

	bool CHexagonMineGame::SetConfig(CHexagonMineGame::Config config)
	{
		if (!config.RandomSize)
		{
			if ((config.Width < 3) || (config.Width > MAX_WIDTH)
				|| (config.Height < 3) || (config.Height > MAX_HEIGHT))
				return false;
		}

		if (config.HexagonShape)
		{
			config.HasRightSideMine = false;
		}

		if (config.MinePercent < MIN_PERCENT)
			config.MinePercent = MIN_PERCENT;
		else if (config.MinePercent > MAX_PERCENT)
			config.MinePercent = MAX_PERCENT;
		
		m_Config = config;
		return true;
	}

	bool CHexagonMineGame::QueryHoleShowStatus(int x, int y, int &value, hexagon_mine::HOLE_STATUS &status) const
	{
		Pos pos = {x, y};
		if (IsHoleValidByVector(pos))
		{
			const Hole& hole = GetHoleByVector(pos);
			status = hole.status;
			value = (hole.status == HMHS_OPEN) ? hole.value : 0;

			return true;
		}

		return false;
	}

	bool CHexagonMineGame::HasDetector() const
	{
		// 剩下有雷标才能探测，否则探出雷的话，雷和雷标的总数量要超出
		return m_IsStarted && m_HasDetector && (m_RemainMines > 0);
	}
}
